exception safety with rust

nomicon 예시를 그대로 들고왔다. 다음 Vec::push_all은 슬라이스 길이를 가지고 미리 reserve를 해서 불필요한 len 체크를 없앴다. 하지만 이 코드는 exception-safe 하지 않다고 한다.

impl<T: Clone> Vec<T> {
    fn push_all(&mut self, to_push: &[T]) {
        self.reserve(to_push.len());
        unsafe {
            // can't overflow because we just reserved this
            self.set_len(self.len() + to_push.len());

            for (i, x) in to_push.iter().enumerate() {
                self.ptr().add(i).write(x.clone());
            }
        }
    }
}

clone 메서드는 본질적으로 위험하다. 왜냐고? Vec 입장에서는 저 클론이 어떻게 이루어지는지 모른다. 사용자가 디폴트 할당자를 사용했을지, 커스텀 할당자를 사용했을지 모른다는 것이다. 만약에 중간에 클론을 하다가 panic이 발생한다면, 프로그램은 stack-unwinding을 수행할 것이고, 나머지 것들이 정리가 안 된 상태로 블럭을 빠져나가게 된다.
블럭을 빠져나가는 것 자체가 문제가 되는 것은 아니다. 하지만 우리는 클론을 하기 전에 벡터의 len을 바꾸지 않았나? 이것이 바로 문제가 된다. uninitialized 된 벡터의 원소를 사용자가 접근할 여지를 남겼기 때문이다. 따라서 이것은 Undefined Behavior이다.

그렇다면, 위 코드를 어떻게 바꾸어야 할까? 저자는 몇가지 방법을 제안한다.

  1. set_len을 매 write 이후에 한 번씩 수행한다.
  2. 루프가 끝난 뒤에야 set_len을 호출한다.